package cn.libinal.scloud.base.service.impl;

import cn.libinal.scloud.base.common.service.impl.AbstractJpaServiceImpl;
import cn.libinal.scloud.base.entity.Menu;
import cn.libinal.scloud.base.entity.Permission;
import cn.libinal.scloud.base.entity.QRole;
import cn.libinal.scloud.base.entity.Role;
import cn.libinal.scloud.base.entity.User;
import cn.libinal.scloud.base.repository.MenuRepository;
import cn.libinal.scloud.base.repository.PermissionRepository;
import cn.libinal.scloud.base.repository.RoleRepository;
import cn.libinal.scloud.base.repository.UserRepository;
import cn.libinal.scloud.base.service.RoleService;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.querydsl.core.types.Predicate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @author libinal
 * created at 2018/11/6 20:35
 */
@Service
public class RoleServiceImpl extends AbstractJpaServiceImpl<Role, RoleRepository> implements RoleService {

    private final MenuRepository menuRepository;
    private final UserRepository userRepository;
    private final PermissionRepository permissionRepository;
    private final QRole qRole = QRole.role;

    public RoleServiceImpl(RoleRepository repository, MenuRepository menuRepository,
                           UserRepository userRepository, PermissionRepository permissionRepository) {
        super(repository);
        this.menuRepository = menuRepository;
        this.userRepository = userRepository;
        this.permissionRepository = permissionRepository;
    }

    @Override
    public Predicate buildPagingPredicates(Role role) {
        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void relateMenusAndPermissions(String roleId, List<String> menuIds, List<String> permissionIds) {
        Assert.notNull(roleId, "分配菜单时角色id不能为空");
        Role role = repository.getOne(roleId);
        Set<String> oldMenuIds = role.getMenus().stream().map(Menu::getId).collect(Collectors.toSet());
        Set<String> oldPermissionIds = role.getPermissions().stream().map(Permission::getId).collect(Collectors.toSet());

        relateMenus(menuIds, role, oldMenuIds);
        relatePermissions(permissionIds,role, oldPermissionIds);

        repository.save(role);
    }

    private void relateMenus(List<String> menuIds, Role role, Set<String> oldMenuIds) {
        oldMenuIds.stream().filter(id -> !menuIds.contains(id))
                .map(id -> CompletableFuture.supplyAsync(() -> menuRepository.findById(id)))
                .map(future -> future.thenApply(Optional::get))
                .map(future -> future.thenAccept(menu -> role.getMenus().remove(menu)))
                .forEach(CompletableFuture::join);

        menuIds.stream().filter(id -> !oldMenuIds.contains(id))
                .map(id -> CompletableFuture.supplyAsync(() -> menuRepository.findById(id)))
                .map(future -> future.thenApply(Optional::get))
                .map(future -> future.thenAccept(menu -> role.getMenus().add(menu)))
                .forEach(CompletableFuture::join);
    }

    private void relatePermissions(List<String> permissionsIds, Role role, Set<String> oldPermissionIds) {
        oldPermissionIds.stream().filter(id -> !permissionsIds.contains(id))
                .map(id -> CompletableFuture.supplyAsync(() -> permissionRepository.findById(id)))
                .map(future -> future.thenApply(Optional::get))
                .map(future -> future.thenAccept(permission -> role.getPermissions().remove(permission)))
                .forEach(CompletableFuture::join);

        permissionsIds.stream().filter(id -> !oldPermissionIds.contains(id))
                .map(id -> CompletableFuture.supplyAsync(() -> permissionRepository.findById(id)))
                .map(future -> future.thenApply(Optional::get))
                .map(future -> future.thenAccept(permission -> role.getPermissions().add(permission)))
                .forEach(CompletableFuture::join);
    }


    @Override
    public Set<Role> getRoleByUserId(String userId) {
        Optional<User> optional = userRepository.findById(userId);
        if (optional.isPresent()) {
            return optional.get().getRoles();
        }
        return Sets.newLinkedHashSet();
    }
}
